package com.group.dao;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.*;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.base.view.ListResult;
import com.group.util.NoteException;

/**
 * 通用DAO层
 * 
 * @author zhaolei
 */
@Component("GeneralBeanDao")
public class GeneralBeanDao {

	private static final Logger logger = LoggerFactory.getLogger(GeneralBeanDao.class);

	@Autowired
	BeanDaoHandler handleDao;

	/**
	 * 当id为空的时候add记录 当id不为空的时候update
	 * 
	 * @param object
	 */
	public void save(@Nullable Object object) {
		Class<? extends Object> clazz = object.getClass();
		String getMethodName = "getId";
		Method getMethod;
		try {
			getMethod = clazz.getMethod(getMethodName, new Class[] {});
			Object value = getMethod.invoke(object);
			if (value == null) {
				add(object);
			} else {
				update(object);
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}

	}

	/**
	 * 传入对象,保存进数据库 对象和表名的关系ClothFlaw 对应 表 clothFlaw 第一个字母变小写,如果表名不同,需要指定表名
	 * 插入的列名和要对象的属性名称一样
	 * 
	 * @param object
	 *            待添加的实体
	 * @param tableName
	 *            表名
	 */
	public void add(@Nullable Object object, String tableName) {
		if (object == null)
			throw new RuntimeException("object不能为空");

		Map<String, Object> para = new HashMap<>();

		// 表名为空,按照默认规则生成表名
		if (StringUtils.isEmpty(tableName)) {
			tableName = getTableName(object);
		}

		para.put("tableName", tableName);
		Class<?> clazz = object.getClass();
		Field idField = handleObject(object, para);

		handleDao.add(para);
		// 设置回Id
		try {
			if (idField != null) {
				String setMethodName = "setId";
				Method setMethod = clazz.getMethod(setMethodName, new Class[] { idField.getType() });
				try {
					// int主键
					setMethod.invoke(object, para.get("id"));
				} catch (Exception e) {
					// long主键
					setMethod.invoke(object, ((Integer) para.get("id")).longValue());
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Nullable
	private Field handleObject(@NotNull Object object, @NotNull Map<String, Object> para) {
		Class<?> clazz = object.getClass();
		Field[] fields = clazz.getDeclaredFields();
		List<Property> pros = new ArrayList<>();
		para.put("properties", pros);
		Field idField = null;

		for (Field field : fields) {
			Property property = new Property();
			String name = field.getName();
			if (name.equalsIgnoreCase("start") || name.equalsIgnoreCase("limit"))
				continue;

			if ("id".equalsIgnoreCase(name)) {
				idField = field;
				continue;
			}
			String firstLetter = name.substring(0, 1).toUpperCase();
			String getMethodName = "get" + firstLetter + name.substring(1);
			try {
				Method getMethod = clazz.getMethod(getMethodName, new Class[] {});

				Object value = getMethod.invoke(object);
				// value为空或者不是基础数据
				if (value == null || !isBasicType(value))
					continue;
				property.setName(name);
				property.setValue(value);
				pros.add(property);
			} catch (Exception ignored) {
				// 跳过没有get的属性
			}
		}

		return idField;
	}

	/**
	 * 根据map插入数据库,返回的map中有id信息
	 * 
	 * 
	 * @param para
	 *            参数键值对
	 * @return para
	 */
	public Map<String, Object> add(Map<String, Object> para) {
		handleDao.add(para);
		return para;
	}

	/**
	 * 传入对象,保存进数据库 对象和表名的关系ClothFlaw 对应 表 clothFlaw 第一个字母变小写,如果表名不同,需要指定表名
	 * 插入的列名和要对象的属性名称一样
	 * 
	 * @param object
	 *            待保存的实体
	 */
	public void add(Object object) {
		add(object, null);
	}

	/**
	 * 传入对象,保存进数据库 对象和表名的关系ClothFlaw 对应 表 clothFlaw 第一个字母变小写,如果表名不同,需要指定表名
	 * 插入的列名和要对象的属性名称一样
	 * 
	 * @param oList
	 *            待保存的实体列表
	 *
	 */
	public void add(@Nullable List<?> oList, String tableName) {
		if (oList == null || oList.size() == 0)
			return;
		for (Object o : oList) {
			add(o, tableName);
		}
	}

	/**
	 * 传入对象,保存进数据库 对象和表名的关系ClothFlaw 对应 表 clothFlaw 第一个字母变小写,如果表名不同,需要指定表名
	 * 插入的列名和要对象的属性名称一样
	 * 
	 * @param oList
	 *            待添加的实体对象列表
	 */
	public void add(List<?> oList) {
		add(oList, null);
	}

	public boolean isBasicType(Object o) {
		return o instanceof String || o instanceof Integer || o instanceof Long || o instanceof Date || o instanceof Double || o instanceof Enum || o instanceof Boolean || o instanceof BigDecimal;
	}

	/**
	 * 通用的更新 对象和表名的关系ClothFlaw 对应 表 clothFlaw 第一个字母变小写,如果表名不同,需要指定表名
	 * 更新的列名和要对象的属性名称一样
	 * 
	 * @param object
	 *            待更新的实体
	 * @param tableName
	 *            表名
	 * @param idName
	 *            根据idName进行更新 , 即where idName = xxx idName必须为object的一个属性
	 */
	public void update(Object object, String tableName, String idName) {
		if (object == null || StringUtils.isEmpty(idName))
			throw new RuntimeException("object和idName不能为空");

		Map<String, Object> para = new HashMap<>();

		// 表名为空,按照默认规则生成表名
		if (StringUtils.isEmpty(tableName)) {
			tableName = getTableName(object);
		}

		para.put("tableName", tableName);

		Class<?> clazz = object.getClass();
		Field[] fields = clazz.getDeclaredFields();
		List<Property> pros = new ArrayList<>();
		para.put("properties", pros);
		Object idValue = null;
		// 设置更新字段
		for (Field field : fields) {
			try {
				Property property = new Property();
				String name = field.getName();
				String firstLetter = name.substring(0, 1).toUpperCase();
				String getMethodName = "get" + firstLetter + name.substring(1);
				Method getMethod = clazz.getMethod(getMethodName, new Class[] {});

				Object value = getMethod.invoke(object);
				if (value != null && !isBasicType(value))
					continue;
				// idName对应的值
				if (idName.equalsIgnoreCase(name))
					idValue = value;
				if (value == null)
					continue;
				property.setName(name);
				property.setValue(value);
				pros.add(property);
			} catch (Exception e) {
				// 跳过没有get的属性
			}

		}

		para.put("idName", idName);
		para.put("idValue", idValue);
		handleDao.update(para);
	}

	/**
	 * 通用的更新 对象和表名的关系ClothFlaw 对应 表 clothFlaw 第一个字母变小写,如果表名不同,需要指定表名
	 * 更新的列名和要对象的属性名称一样
	 * 
	 * @param object
	 *            待更新的实体
	 * @param idName
	 *            根据idName进行更新 , 即where idName = xxx idName必须为object的一个属性
	 */
	public void update(Object object, String idName) {
		update(object, null, idName);
	}

	/**
	 * 根据id字段update
	 * 
	 * @param object
	 */
	public void update(Object object) {
		update(object, null, "id");
	}

	/**
	 * 通用的删除 对象和表名的关系ClothFlaw 对应 表 clothFlaw 第一个字母变小写,如果表名不同,需要指定表名
	 * 
	 * @param object
	 *            待删除的实体
	 * @param tableName
	 *            表名
	 * @param idName
	 *            删除条件名称,根据idName进行删除, dName必须为object的一个属性
	 */
	public void delete(@Nullable Object object, String tableName, String idName) {
		if (object == null || StringUtils.isEmpty(idName))
			throw new RuntimeException("object和idName不能为空");

		Map<String, Object> para = new HashMap<>();

		// 表名为空,按照默认规则生成表名
		if (StringUtils.isEmpty(tableName)) {
			tableName = getTableName(object);
		}
		para.put("tableName", tableName);
		para.put("idName", idName);

		try {
			Class<?> clazz = object.getClass();
			Field field = clazz.getDeclaredField(idName);
			if (field == null)
				throw new Exception("idName 在object中不存在");
			String name = field.getName();

			String firstLetter = name.substring(0, 1).toUpperCase();
			String getMethodName = "get" + firstLetter + name.substring(1);
			Method getMethod = clazz.getMethod(getMethodName, new Class[] {});

			Object idValue = getMethod.invoke(object);

			para.put("idValue", idValue);
			handleDao.delete(para);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 
	 * @param object
	 *            待删除实体
	 * @param idName
	 *            主键字段名
	 */
	public void delete(Object object, String idName) {
		delete(object, null, idName);
	}

	/**
	 * 根据id删除
	 * 
	 * @param clazz
	 * @param value
	 */
	public <T> void quickDeleteById(Class<T> clazz, Object value) {
		quickDelete(clazz, "id", value);
	}

	/**
	 * 删除
	 * 
	 * @param clazz
	 * @param name
	 * @param value
	 */
	public <T> void quickDelete(Class<T> clazz, String name, Object value) {
		try {
			T object = (T) clazz.newInstance();
			Field field = clazz.getDeclaredField(name);
			String fieldName = field.getName();
			String firstLetter = fieldName.substring(0, 1).toUpperCase();
			String setMethodName = "set" + firstLetter + fieldName.substring(1);
			Method method = clazz.getDeclaredMethod(setMethodName, field.getType());
			method.invoke(object, value);

			delete(object, null, name);
		} catch (InstantiationException | IllegalAccessException | NoSuchFieldException | SecurityException | NoSuchMethodException | IllegalArgumentException | InvocationTargetException e) {
			e.printStackTrace();
			throw new NoteException(e.getLocalizedMessage());
		}
	}

	public String getTableName(@NotNull Object object) {
		String tableName;
		String className = object.getClass().getName();
		tableName = className.substring(className.lastIndexOf(".") + 1);
		char[] chs = tableName.toCharArray();
		if (chs.length > 0)
			chs[0] = Character.toLowerCase(chs[0]);

		tableName = new String(chs);

		return tableName;
	}

//	通用的分页查询 根据object中的不为空的属性进行查询 对象和表名的关系ClothFlaw 对应 表 clothFlaw
//	 * 第一个字母变小写,如果表名不同,需要指定表名 支持like查询方式,需要like查询的属性以likeProperties字符串链表的方式传入,
//	 * 传入的属性要在object中存在 支持自定义sql片段查询. List<String> userPara = new ArrayList<>();
//	 * userPara.add("and name like '%time%'");
//	 *
//			 * 带分页的查询 start,limit
	/**
	 * 
	 * @param <T>
	 * @param <T>
	 * 
	 * @param object
	 *            等值查询条件
	 * @param tableName
	 *            表名
	 * @param likeProperties
	 *            相似查询条件
	 */
	@SuppressWarnings("unchecked")
	@NotNull
	public <T> ListResult<T> query(@Nullable T object, String tableName, @Nullable List<String> likeProperties, @Nullable List<String> userPara, Integer start, Integer limit) {
		if (likeProperties == null)
			likeProperties = new ArrayList<>();

		if (userPara == null)
			userPara = new ArrayList<>();

		ListResult<T> listResult = new ListResult<>();
		List<T> resultList = new ArrayList<>();
		listResult.setList(resultList);

		if (object == null)
			throw new RuntimeException("object不能为空");

		Map<String, Object> para = new HashMap<>();

		// 表名为空,按照默认规则生成表名
		if (StringUtils.isEmpty(tableName)) {
			tableName = getTableName(object);
		}
		para.put("tableName", tableName);

		Class<?> clazz = object.getClass();
		Field[] fields = clazz.getDeclaredFields();
		// 普通属性
		List<Property> pros = new ArrayList<>();
		// like属性
		List<Property> likePros = new ArrayList<>();
		para.put("properties", pros);
		para.put("likeProperties", likePros);
		// 用户自己的sql语句片段
		para.put("userPara", userPara);
		para.put("start", start);
		para.put("limit", limit);
		// 设置查询字段
		for (Field field : fields) {
			try {
				Property property = new Property();
				String name = field.getName();
				// 因为mybatis映射Object里的list时用的是get方法，而如果get得到一个null会报空指针，所以全都返回一个空list，这里会产生冲突，所以过滤掉list类型属性
				Type type = field.getType();
				if (type.toString().equals("interface java.util.List")) {
					continue;
				}
				String firstLetter = name.substring(0, 1).toUpperCase();
				String getMethodName = "get" + firstLetter + name.substring(1);
				Method getMethod = clazz.getMethod(getMethodName, new Class[] {});

				Object value = getMethod.invoke(object);
				if (value == null)
					continue;
				property.setName(name);
				property.setValue(value);
				if (likeProperties.contains(name))
					likePros.add(property);
				else
					pros.add(property);
			} catch (Exception e) {
				// 跳过没有get的属性
			}

		}

		List<Map<String, Object>> result = handleDao.query(para);

		// 查询结果封装为Object
		for (Map<String, Object> map : result) {

			T resultObject = null;
			try {
				resultObject = (T) object.getClass().newInstance();
			} catch (InstantiationException | IllegalAccessException e) {
				e.printStackTrace();
			}

			resultList.add(resultObject);

			Set<String> keySet = map.keySet();
			for (String aKeySet : keySet) {
				try {
					Object value = map.get(aKeySet);
					// 值是空,则不映射到对象
					if (value == null || !isBasicType(value)) {
						continue;
					}

					Field field = clazz.getDeclaredField(aKeySet);
					if (field == null) {
						continue;
					}

					String name = field.getName();
					String firstLetter = name.substring(0, 1).toUpperCase();
					String setMethodName = "set" + firstLetter + name.substring(1);
					Method method = clazz.getDeclaredMethod(setMethodName, field.getType());

					if (field.getType().isEnum()) {
						// 枚举类型
						Object objects[] = field.getType().getEnumConstants();
						for (Object o : objects) {
							if (value.toString().equalsIgnoreCase(o.toString())) {
								method.invoke(resultObject, o);
							}
						}

					}
					if (field.getType().toString().contains("Boolean")) {
						if ((int) value == 0)
							method.invoke(resultObject, Boolean.FALSE);
						else
							method.invoke(resultObject, Boolean.TRUE);
					} else {
						method.invoke(resultObject, value);
					}
				} catch (Exception ignored) {
				}

			}
		}

		int count = handleDao.queryCount(para);
		listResult.setTotal(count);
		return listResult;
	}

	//通用的分页查询 根据object中的不为空的属性进行查询 对象和表名的关系ClothFlaw 对应 表 clothFlaw
	//	 * 第一个字母变小写,如果表名不同,需要指定表名 支持like查询方式,需要like查询的属性以likeProperties字符串链表的方式传入,
	//	 * 传入的属性要在object中存在 支持自定义sql片段查询. List<String> userPara = new ArrayList<>();
	//	 * userPara.add("and name like '%time%'"); 不需要分页
	/**
	 *
	 * @param <T>
	 * @param <T>
	 * 
	 * 
	 * @param object
	 *            等值查询条件
	 * @param tableName
	 *            表名
	 * @param likeProperties
	 *            相似查询条件
	 */
	@NotNull
	public <T> ListResult<T> query(@Nullable T object, String tableName, @Nullable List<String> likeProperties, @Nullable List<String> userPara) {
		return query(object, tableName, likeProperties, userPara, null, null);
	}

	/**
	 * 通用的分页查询 根据object中的不为空的属性进行查询 对象和表名的关系ClothFlaw 对应 表 clothFlaw
	 * 第一个字母变小写,如果表名不同,需要指定表名
	 * 
	 * @param <T>
	 * 
	 * 
	 * 
	 * @param object
	 *            查询条件
	 * @param tableName
	 *            表名
	 */
	@NotNull
	public <T> ListResult<T> query(T object, String tableName) {
		return query(object, tableName, null, null);
	}

	/**
	 * 默认表名,不分页
	 * 
	 * @param <T>
	 * 
	 * @param object
	 *            查询条件
	 * @return 查询结果
	 */
	@NotNull
	public <T> ListResult<T> query(T object) {
		return query(object, null, null, null);
	}

	/**
	 * 默认表名,不分页 , 包含like属性
	 * 
	 * @param <T>
	 * 
	 * @param object
	 *            查询条件
	 * @return 查询结果
	 */
	@NotNull
	public <T> ListResult<T> query(T object, List<String> likeProperties) {

		return query(object, null, likeProperties, null);
	}

	/**
	 * 查询单条结果
	 * 
	 * @param object
	 *            查询条件
	 * @return 查询结果
	 */
	@Nullable
	public <T> T queryOne(T object) {

		try {
			return query(object, null, null, null).getList().get(0);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 查询单条结果,包含like属性
	 * 
	 * @param object
	 *            查询条件
	 * @return 查询结果
	 */
	@Nullable
	public <T> T queryOne(T object, String tableName, List<String> likeProperties) {

		try {
			return query(object, tableName, likeProperties, null).getList().get(0);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 查询单条结果,包含like属性
	 * 
	 * @param <T>
	 * 
	 * @param object
	 *            查询条件
	 * @return 单条结果（查不到就null）
	 */
	@Nullable
	public <T> T queryOne(T object, List<String> likeProperties) {
		try {
			return query(object, null, likeProperties, null).getList().get(0);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 查询单条结果
	 * 
	 * @param object
	 *            查询条件
	 * @return 查询结果
	 */
	@Nullable
	public <T> T queryOne(T object, String tableName) {

		try {
			return query(object, tableName, null, null).getList().get(0);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 根据id查询单条记录
	 * 
	 * @param clazz
	 * @param value
	 * @return
	 */
	public <T> T quickQueryOneById(@NotNull Class<T> clazz, Object value) {

		return quickQueryOne(clazz, "id", value);
	}

	/**
	 * 快速查询只有一个查询参数的对象
	 * 
	 * @param clazz
	 *            待查询的实体类型
	 * @param name
	 *            参数名
	 * @param value
	 *            参数值
	 * @return 单条结果
	 */
	@Nullable
	public <T> T quickQueryOne(@NotNull Class<T> clazz, String name, Object value) {
		if (value == null)
			return null;

		try {
			T object = (T) clazz.newInstance();
			Field field = clazz.getDeclaredField(name);
			String fieldName = field.getName();
			String firstLetter = fieldName.substring(0, 1).toUpperCase();
			String setMethodName = "set" + firstLetter + fieldName.substring(1);
			Method method = clazz.getDeclaredMethod(setMethodName, field.getType());
			method.invoke(object, value);

			return queryOne(object);
		} catch (InstantiationException | IllegalAccessException | NoSuchFieldException | SecurityException | NoSuchMethodException | IllegalArgumentException | InvocationTargetException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 查询list结果,不分页
	 * 
	 * @param <T>
	 * 
	 * @param object
	 *            查询条件
	 * @param likeProperties
	 *            相似查询条件
	 * @return 结果列表
	 */
	@Nullable
	public <T> List<T> queryList(T object, List<String> likeProperties) {
		try {
			return query(object, null, likeProperties, null).getList();
		} catch (Exception e) {
			return null;
		}

	}

	/**
	 * 查询list结果,不分页,包含like属性
	 * 
	 * @param <T>
	 * 
	 * @param object
	 *            查询条件
	 * @return 结果列表
	 */
	@Nullable
	public <T> List<T> queryList(T object) {
		try {
			return query(object, null, null, null).getList();
		} catch (Exception e) {
			return null;
		}

	}

	/**
	 * 根据id查询列表
	 * 
	 * @param clazz
	 * @param value
	 * @return
	 */
	public <T> List<T> quickQueryListById(@NotNull Class<T> clazz, Object value) {

		return quickQueryList(clazz, "id", value);
	}

	/**
	 * 快速查询只有一个参数的列表
	 * 
	 * @param <T>
	 * 
	 * @param clazz
	 *            待查询的实体类型
	 * @param name
	 *            参数名
	 * @param value
	 *            参数值
	 * @return 结果列表
	 */
	@Nullable
	public <T> List<T> quickQueryList(@NotNull Class<T> clazz, String name, Object value) {
		try {
			T object = clazz.newInstance();
			Field field = clazz.getDeclaredField(name);
			String fieldName = field.getName();
			String firstLetter = fieldName.substring(0, 1).toUpperCase();
			String setMethodName = "set" + firstLetter + fieldName.substring(1);
			Method method = clazz.getDeclaredMethod(setMethodName, field.getType());
			method.invoke(object, value);

			return queryList(object);
		} catch (InstantiationException | IllegalAccessException | NoSuchFieldException | SecurityException | NoSuchMethodException | IllegalArgumentException | InvocationTargetException e) {
			e.printStackTrace();
			return null;
		}
	}

	//可以自己拼写sql条件. exp: List<String> userPara = new ArrayList<>();
	//	 * userPara.add("and name like '%time%'");
	/**
	 *
	 * @param <T>
	 * 
	 * @param object
	 *            查询条件
	 * @param userPara
	 *            自定义sql片段
	 * @return 结果列表
	 */
	@Nullable
	public <T> List<T> queryListByPara(T object, String tableName, List<String> userPara) {
		try {
			return query(object, tableName, null, userPara).getList();
		} catch (Exception e) {
			System.out.println(e.getMessage());
			return null;
		}
	}

	/**
	 * 根据属性的多个值查询指定对象
	 * 
	 * @param cls
	 *            目标对象的类
	 * @param tableName
	 *            表名
	 * @param propName
	 *            属性名
	 * @param values
	 *            多个值
	 * @return 查询到的对象列表
	 */
	public <T> List<T> queryByMultiValue(Class<T> cls, String tableName, String propName, List values) {
		List<Map<String, Object>> dbRows = handleDao.queryByMultiValue(tableName, propName, values);

		List<T> result = new ArrayList<>();

		if (dbRows.isEmpty())
			return result;

		/*
		 * for (Map<String, Object> row : dbRows) { try { T t =
		 * cls.newInstance(); BeanUtils.populate(t, row); result.add(t); } catch
		 * (InstantiationException | IllegalAccessException |
		 * InvocationTargetException e) { logger.error(e.getMessage()); throw
		 * new RuntimeException(e); } }
		 */
		return result;
	}
}
